

package com.hazelcast.test.starter.answer;

import com.hazelcast.cache.impl.ICacheRecordStore;
import com.hazelcast.cache.impl.PreJoinCacheConfig;
import com.hazelcast.config.CacheConfig;
import com.hazelcast.config.InMemoryFormat;
import com.hazelcast.internal.services.ObjectNamespace;
import com.hazelcast.map.impl.MapServiceContext;
import com.hazelcast.multimap.impl.MultiMapPartitionContainer;
import com.hazelcast.spi.impl.NodeEngine;
import org.mockito.invocation.InvocationOnMock;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import static com.hazelcast.test.starter.ReflectionUtils.getFieldValueReflectively;
import static org.mockito.Mockito.mock;

/**
 * Default {@link org.mockito.stubbing.Answer} to create a mock for a proxied
 * Hazelcast {@code Service}.
 */
class ServiceAnswer extends AbstractAnswer {

    private final Class<?> objectNamespaceClass;
    private final Class<?> preJoinCacheConfigClass;
    private final Class<?> cacheConfigClass;
    private final Class<?> inMemoryFormatClass;

    ServiceAnswer(Object delegate) throws Exception {
        super(delegate);
        objectNamespaceClass = delegateClassloader.loadClass(ObjectNamespace.class.getName());
        preJoinCacheConfigClass = delegateClassloader.loadClass(PreJoinCacheConfig.class.getName());
        cacheConfigClass = delegateClassloader.loadClass(CacheConfig.class.getName());
        inMemoryFormatClass = delegateClassloader.loadClass(InMemoryFormat.class.getName());
    }

    @Override
    Object answer(InvocationOnMock invocation, String methodName, Object[] arguments) throws Exception {
        if (arguments.length == 2 && methodName.equals("getContainerOrNull")) {
            // RingbufferService
            Method delegateMethod = getDelegateMethod(methodName, Integer.TYPE, objectNamespaceClass);
            return invoke(delegateMethod, arguments);
        } else
            if (arguments.length == 2 && methodName.equals("getOrCreateContainer")) {
                // QueueService
                Method delegateMethod = getDelegateMethod(methodName, String.class, Boolean.TYPE);
                return invoke(delegateMethod, arguments);
            } else
                if (arguments.length == 1 && methodName.equals("getExistingContainerOrNull")) {
                    // QueueService
                    Method delegateMethod = getDelegateMethod(methodName, String.class);
                    return invoke(delegateMethod, arguments);
                } else
                    if (arguments.length == 1 && (methodName.equals("getCardinalityEstimatorContainer"))) {
                        // CardinalityEstimatorService
                        Method delegateMethod = getDelegateMethod(methodName, String.class);
                        return invoke(delegateMethod, arguments);
                    } else
                        if (arguments.length == 1 && methodName.equals("getPartitionContainer")) {
                            // MultiMapService
                            Method delegateMethod = getDelegateMethod(methodName, Integer.TYPE);
                            return getMultiMapPartitionContainer(delegateMethod, arguments);
                        } else
                            if (arguments.length == 2 && methodName.equals("getCacheOperationProvider")) {
                                // CacheService
                                Method delegateMethod = delegateClass.getMethod(methodName, String.class, inMemoryFormatClass);
                                return invoke(delegateMethod, arguments);
                            } else
                                if (arguments.length == 2 && methodName.equals("getRecordStore")) {
                                    // CacheService
                                    Method delegateMethod = delegateClass.getMethod(methodName, String.class, Integer.TYPE);
                                    return getICacheRecordStore(delegateMethod, arguments);
                                } else
                                    if (arguments.length == 1 && (methodName.equals("getCacheConfig") || methodName.equals("findCacheConfig"))) {
                                        // CacheService
                                        return invoke(invocation, arguments);
                                    } else
                                        if (arguments.length == 1 && methodName.equals("createCacheConfigOnAllMembers")) {
                                            // CacheService
                                            Method delegateMethod = delegateClass.getMethod(methodName, preJoinCacheConfigClass);
                                            return invoke(delegateMethod, arguments);
                                        } else
                                            if (arguments.length == 1 && methodName.equals("putCacheConfigIfAbsent")) {
                                                // CacheService
                                                Method delegateMethod = delegateClass.getMethod(methodName, cacheConfigClass);
                                                return invoke(delegateMethod, arguments);
                                            } else
                                                if (arguments.length == 1 && methodName.equals("deleteCacheConfig")) {
                                                    // CacheService
                                                    return invoke(invocation, arguments);
                                                } else
                                                    if (arguments.length == 1 && (methodName.equals("addLifecycleListener"))) {
                                                        // LifecycleService
                                                        // FIXME
                                                        return null;
                                                    } else
                                                        if (arguments.length == 1 && (methodName.equals("removeLifecycleListener"))) {
                                                            // LifecycleService
                                                            // FIXME
                                                            return null;
                                                        } else
                                                            if (arguments.length == 0 && methodName.equals("getMapServiceContext")) {
                                                                // MapService
                                                                Object mapServiceContext = invokeForMock(invocation);
                                                                return mock(MapServiceContext.class, new MapServiceContextAnswer(mapServiceContext));
                                                            } else
                                                                if (arguments.length == 0 && methodName.startsWith("isRunning")) {
                                                                    // LifecycleService
                                                                    return invoke(invocation);
                                                                } else
                                                                    if (arguments.length == 0 && methodName.startsWith("get")) {
                                                                        return invoke(invocation);
                                                                    }
        throw new UnsupportedOperationException("Method is not implemented in ServiceAnswer: " + methodName);
    }

    private Object getMultiMapPartitionContainer(Method delegateMethod, Object[] arguments) throws Exception {
        Object container = delegateMethod.invoke(delegate, arguments);
        if (container == null) {
            return null;
        }
        return mock(MultiMapPartitionContainer.class, new PartitionContainerAnswer(container));
    }

    private Object getICacheRecordStore(Method delegateMethod, Object[] arguments) throws Exception {
        Object recordStore = delegateMethod.invoke(delegate, arguments);
        if (recordStore == null) {
            return null;
        }
        return mock(ICacheRecordStore.class, new RecordStoreAnswer(recordStore));
    }

    /**
     * Assuming delegate has a field {@code nodeEngine}, returns the serialization
     * service from {@link NodeEngine#getSerializationService()}.
     */
    Object getSerializationService() {
        try {
            Object nodeEngine = getFieldValueReflectively(delegate, "nodeEngine");
            Method getter = nodeEngine.getClass().getDeclaredMethod("getSerializationService");
            return getter.invoke(nodeEngine);
        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            throw new UnsupportedOperationException("Could not invoke " + "nodeEngine#getSerializationService()", e);
        }
    }
}
